/* 
 * Copyright (c) 2020 Omar Vega Ramos.
 * 
 * This program is free software: you can redistribute it and/or modify  
 * it under the terms of the GNU General Public License as published by  
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version..
 *
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cstring>
#include <map>
#include <vector>
#include <expat.h>

struct user_data
{
  const char *output_osm_file;
  std::map<std::string, std::vector<std::pair <double, double>>> placesByname;
  std::pair <double, double> coordinate;
  std::string subdistrict;
  int is_node;
  int is_ie;
};

bool
in_array(std::vector<size_t> vector, size_t element)
{
  if ( std::find(vector.begin(), vector.end(), element) != vector.end() ) {
    return true;
  } else {
    return false;
  }
}

void
start_element(void *data, const char *element, const char **attrs)
{
  struct user_data *d = (struct user_data *) data;

  if (strcmp(element, "node") == 0) {
    double lat = atof(attrs[3]);
    double lon = atof(attrs[5]);
    std::pair <double, double> coordinate (lat, lon);
    d->coordinate = coordinate;
    d->is_node = 1;
    d->is_ie = 0;
    d->subdistrict = "";
  }

  if ((strcmp(element, "tag") == 0) &&
      (d->is_node == 1)) {
    if (strcmp(attrs[1], "addr:subdistrict") == 0) {
      d->subdistrict = attrs[3];
    }

    if (((strcmp(attrs[1], "amenity") == 0) &&
	 (strcmp(attrs[3], "school") == 0)) ||
	((strcmp(attrs[1], "amenity") == 0) &&
	 (strcmp(attrs[3], "kindergarten") == 0))) {
      d->is_ie = 1;
    }
  }
}

void
end_element(void *data, const char *element)
{
  struct user_data *d = (struct user_data *) data;

  if (strcmp(element, "node") == 0) {
    if ((d->is_ie == 1) && (d->subdistrict != "")) {
      std::string name = d->subdistrict;

      if ( d->placesByname.find(name) == d->placesByname.end() ) {
	std::vector<std::pair <double, double>> coordinates;
	coordinates.push_back(d->coordinate);
	d->placesByname[name] = coordinates;
      } else {
	d->placesByname[name].push_back(d->coordinate);
      }

      d->is_node = 0;
    }
  }

  if (strcmp(element, "osm") == 0) {
    std::map<std::string, std::vector<std::pair <double, double>>>::iterator it;
    std::vector<size_t> removed_ids;

    for ( it = d->placesByname.begin(); it != d->placesByname.end(); it++ ) {
      std::string name = it->first;
      std::vector<std::pair <double, double>> coordinates = it->second;
      size_t coordinates_size = coordinates.size();

      for (size_t i = 0; i < coordinates_size - 1; i++) {
	for (size_t j = i + 1; j < coordinates_size; j++) {
	  if ( (in_array(removed_ids, i) == false) && (in_array(removed_ids, j) == false) ) {
	    double dlat = abs(coordinates[i].first - coordinates[j].first);
	    double dlon = abs(coordinates[i].second - coordinates[j].second);

	    if ((dlat < 0.025) && (dlon < 0.025)) {
	      coordinates[i].first = (coordinates[i].first + coordinates[j].first) / 2;
	      coordinates[i].second = (coordinates[i].second + coordinates[j].second) / 2;
	      removed_ids.push_back(j);
	    }
	  }
	}
      }

      d->placesByname[name] = coordinates;
    }

    FILE *fp;
    fp = fopen (d->output_osm_file, "w");

    if (fp == NULL) {
      fprintf(stderr, "Could not read %s\n", d->output_osm_file);
      exit(-1);
    }

    fprintf (fp, "<?xml version='1.0' encoding='UTF-8'?>\n");
    fprintf (fp, "<osm version=\"0.6\">\n");
    size_t j = 1;

    for ( it = d->placesByname.begin(); it != d->placesByname.end(); it++ ) {
      std::string name = it->first;
      std::vector<std::pair <double, double>> coordinates = it->second;
      size_t coordinates_size = coordinates.size();

      for (size_t i = 0; i < coordinates_size; i++) {
	if ( in_array(removed_ids, i) == false ) {
	  fprintf (fp, "  <node id=\"-%ld\" lat=\"%f\" lon=\"%f\" version=\"1\" timestamp=\"2019-04-03T16:45:00Z\" changeset=\"0\">\n", j, coordinates[i].first, coordinates[i].second);
	  fprintf (fp, "    <tag k=\"NAME\" v=\"%s\"/>\n", name.c_str());
	  fprintf (fp, "  </node>\n");
	}

	j = j + 1;
      }
    }

    fprintf (fp, "</osm>\n");
    printf("Generated ´%s´ file.\n", d->output_osm_file);
    fclose (fp);
  }
}

int
main(int argc, char *argv[])
{
  if (argc < 2) {
    fprintf (stderr, "Usage: %s <input_osm_file> [<output_osm_file>]\n", argv[0]);
    exit (-1);
  }

  FILE *fp = fopen (argv[1], "r");

  if (fp == NULL) {
    fprintf (stderr, "Could not read %s\n", argv[1]);
    exit (-1);
  }

  const char *output_osm_file = "output.osm";

  if (argc > 2) {
    output_osm_file = argv[2];
  }

  struct user_data data;
  data.output_osm_file = output_osm_file;

  size_t len;
  char buf[1024];
  size_t done;

  XML_Parser parser = XML_ParserCreate (NULL);
  XML_SetUserData(parser, &data);
  XML_SetElementHandler (parser, start_element, end_element);

  do {
    len =  fread (buf, 1, sizeof(buf), fp);
    done = len < sizeof (buf);

    if (XML_Parse (parser, buf, len, done) == XML_STATUS_ERROR) {
      int code = XML_GetCurrentLineNumber (parser);
      const char *msg = XML_ErrorString (XML_GetErrorCode(parser));
      fprintf (stderr, "%s at line %d\n", msg, code);
      exit (-1);
    }
  } while (!done);

  XML_ParserFree (parser);
  fclose (fp);

  return 0;
}
